type FactoryFunction<T> = () => T;

const DEFAULT_POOL_LENGTH = 40;

export function createMap(forceDictMode?: boolean): any {
    const map = Object.create(null);
    if (forceDictMode) {
        const INVALID_IDENTIFIER_1 = '.';
        const INVALID_IDENTIFIER_2 = '/';
        // assign dummy values on the object
        map[INVALID_IDENTIFIER_1] = 1;
        map[INVALID_IDENTIFIER_2] = 1;
        delete map[INVALID_IDENTIFIER_1];
        delete map[INVALID_IDENTIFIER_2];
    }
    return map;
}

export class Pool<T> {
    private _count: number;
    private _pool: Array<T | null>;
    private _usedMap: Record<string, Array<T | null>>;
    private _factory: FactoryFunction<T> | null;

    constructor(pool_size?: number, factory?: FactoryFunction<T>) {
        this._count = 0;
        this._pool = new Array(pool_size ?? DEFAULT_POOL_LENGTH);
        this._usedMap = createMap(true);
        this._factory = (factory === undefined) ? null : factory;
    }

    public setFactoryFunction(factory: FactoryFunction<T>) {
        this._factory = factory;
    }

    public get(user?: string, factory?: FactoryFunction<T>): T | null {
        let ret = this.__get();

        if (!ret) {
            if (factory)
                ret = factory();
            else if (this._factory)
                ret = this._factory();
        }

        if (ret && user) {
            let usedArr = this._usedMap[user];
            if (!usedArr) {
                usedArr = new Array();
                this._usedMap[user] = usedArr;
            }
            usedArr[usedArr.length] = ret;
        }

        return ret;
    }

    public put(obj: T, user?: string) {
        const pool = this._pool;
        if (this._count < pool.length) {
            pool[this._count] = obj;
            ++this._count;
        }

        if (user) {
            let usedArr = this._usedMap[user];
            if (usedArr) {
                for (let i = usedArr.length - 1; i > -1; i--) {
                    const tmpObj = usedArr[i];
                    if (tmpObj == obj) {
                        usedArr.splice(i, 1);
                    }
                }
            }
        }

    }

    public recycleAll(user: string) {
        let usedArr = this._usedMap[user];
        if (usedArr) {
            for (let i = 0; i < usedArr.length; i++) {
                const tmpObj = usedArr[i];
                this.put(tmpObj);
            }
            usedArr.length = 0;
        }

        delete this._usedMap[user];
    }

    private __get() {
        if (this._count > 0) {
            --this._count;

            const cache = this._pool[this._count];
            this._pool[this._count] = null;
            return cache;
        }

        return null;
    }

}